package com.enfi.api.modules.ext.websocket.handler;

import com.enfi.api.modules.ext.websocket.domain.GroupSession;
import com.enfi.api.modules.ext.websocket.domain.GroupUser;
import com.enfi.api.modules.ext.websocket.domain.MyPrincipal;
import com.enfi.api.util.SpringContextUtil;
import com.google.common.collect.Sets;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.simp.user.SimpUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * channel 处理拦截
 *
 * @author xukk
 * @date 2020/3/9
 */
@Slf4j
@Service
public class ChannelInInterceptor implements ChannelInterceptor {
    private SimpUserRegistry userRegistry;

    /**
     * 在消息发送之前调用，方法中可以对消息进行修改，如果此方法返回值为空，则不会发生实际的消息发送调用
     *
     * @param message 消息
     * @param channel 通道
     * @return the Message
     */
    @Nullable
    @Override
    @SneakyThrows
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
            if (raw instanceof Map) {
                Object name = ((Map) raw).get("userCode");
                if(name==null){
                    name=((Map) raw).get("username");
                }
                Object group = ((Map) raw).get("group");
                if (name != null && name instanceof List) {
                    // 设置当前访问的认证用户
                    List<String> arr = (List) name;
                    if (!CollectionUtils.isEmpty(arr)) {
                        String username = arr.get(0).toLowerCase();
                        MyPrincipal myPrincipal = new MyPrincipal(username);
                        if (group != null && group instanceof List) {
                            // 设置当前访问的认证用户组
                            List<String> arr1 = (List) group;
                            if (!CollectionUtils.isEmpty(arr)) {
                                String groupName = arr1.get(0).toLowerCase();
                                myPrincipal.setGroup(groupName);
                                if (GroupSession.getGroup().containsKey(groupName)) {
                                    GroupSession.getGroup().get(groupName).add(username);
                                } else {
                                    GroupSession.getGroup().put(groupName, Sets.newConcurrentHashSet());
                                }
                                GroupUser.getGroup().put(username, groupName);
                            }
                        }
                        accessor.setUser(myPrincipal);
                    }
                } else {
                    return null;
                }


            }
        }
        return message;
    }

    /**
     * 在消息发送后立刻调用，boolean值参数表示该调用的返回值
     *
     * @param message 消息
     * @param channel 通道
     * @param sent    发送标识位
     */
    @Override
    public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        String sessionId = accessor.getHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER).toString();
        // 忽略心跳消息等非STOMP消息
        if (accessor.getCommand() == null) {
            return;
        }
        // 根据连接状态做处理，这里也只是打印了下，可以根据实际场景，对上线，下线，首次成功连接做处理
        switch (accessor.getCommand()) {
            // 首次连接
            case CONNECT:
                log.info(" 上线");
                if (getSimpUserRegistry() != null) {
                    log.info("当前在线人数：{}", getSimpUserRegistry().getUserCount());
                }
                break;
            case ERROR:
                log.warn("error");
                break;
            case MESSAGE:
                log.warn("message");
                break;
            // 下线
            case DISCONNECT:
                log.info(" 下线");
                MyPrincipal myPrincipal = (MyPrincipal) accessor.getUser();
                if (myPrincipal!=null&&getSimpUserRegistry() != null) {
                    log.info("当前在线人数：{}", getSimpUserRegistry().getUserCount());
                    SimpUser simpUser = getSimpUserRegistry().getUser(myPrincipal.getName());
                    if (simpUser == null) {
                        clear(myPrincipal);
                    } else {
                        if (simpUser.getSessions().stream().filter(a -> a.getId().equalsIgnoreCase(sessionId)).count() == 0) {
                            clear(myPrincipal);
                        }
                    }
                }

                break;
            default:
                break;
        }
    }

    public SimpUserRegistry getSimpUserRegistry() {
        if (this.userRegistry == null) {
            try {
                this.userRegistry = SpringContextUtil.getApplicationContext().getBean(SimpUserRegistry.class);
            } catch (Exception e) {

            }
        }
        return this.userRegistry;
    }

    public void clear(MyPrincipal myPrincipal) {
        if (myPrincipal.getGroup()!=null&&GroupSession.getGroup().containsKey(myPrincipal.getGroup())) {
            GroupSession.getGroup().get(myPrincipal.getGroup()).remove(myPrincipal.getName());
        }
    }

    /**
     * 1. 在消息发送完成后调用，而不管消息发送是否产生异常，在次方法中，我们可以做一些资源释放清理的工作
     * 2. 此方法的触发必须是preSend方法执行成功，且返回值不为null,发生了实际的消息推送，才会触发
     *
     * @param message 消息
     * @param channel 通道
     * @param sent    标识位
     * @param ex      异常
     */
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, @Nullable Exception ex) {

    }

    /**
     * 1. 在消息被实际检索之前调用，如果返回false,则不会对检索任何消息，只适用于(PollableChannels)，
     * 2. 在websocket的场景中用不到
     *
     * @param channel 通道
     * @return
     */
    @Override
    public boolean preReceive(MessageChannel channel) {
        return false;
    }

    /**
     * 1. 在检索到消息之后，返回调用方之前调用，可以进行信息修改，如果返回null,就不会进行下一步操作
     * 2. 适用于PollableChannels，轮询场景
     *
     * @param message 消息
     * @param channel 通道
     * @return
     */
    @Nullable
    @Override
    public Message<?> postReceive(Message<?> message, MessageChannel channel) {
        return null;
    }

    /**
     * 1. 在消息接收完成之后调用，不管发生什么异常，可以用于消息发送后的资源清理
     * 2. 只有当preReceive 执行成功，并返回true才会调用此方法
     * 2. 适用于PollableChannels，轮询场景
     *
     * @param message 消息
     * @param channel 通道
     * @param ex      异常
     */
    @Override
    public void afterReceiveCompletion(@Nullable Message<?> message, MessageChannel channel, @Nullable Exception ex) {

    }
}
